package distributedbank.server.messages;

import java.io.IOException;

import distributedbank.Main;
import distributedbank.Wrapper;
import distributedbank.gui.messages.BalanceResponse;
import distributedbank.gui.messages.ConnectionError;
import distributedbank.server.BankBranch;

/**
 * A request to transfer a certain amount of cash from an account in the current
 * bank branch to an account at a different bank branch.
 */
public class Transfer
        extends Transaction {
    public static final long serialVersionUID = 1;

    private final int destBranchId;
    private final int destAccountId;
    private final int amount; // amount to transfer in cents

    /**
     * Note: the transactionId must be globally unique!
     */
    public Transfer(int branchId, int accountId, int transactionId,
            int fromPort, int destBranchId, int destAccountId, int amount) {
        super(branchId, accountId, transactionId, fromPort);
        this.destBranchId = destBranchId;
        this.destAccountId = destAccountId;
        this.amount = amount;
    }

    public boolean innerBranch() {
        return this.destBranchId == this.branchId ? true : false;
    }

    /**
     * Runs the request and sends a TransferDeposit message, if necessary.
     */
    @Override
    public void runRequest(BankBranch bankBranch, Wrapper wrapper)
            throws IOException, ClassNotFoundException {
        wrapper.sendSyncRequests(this);

        BalanceResponse response = (BalanceResponse) this
                .runRequestSilent(bankBranch);

        TransferDeposit depositMessage = new TransferDeposit(this.destBranchId,
                this.destAccountId, this.transactionId, wrapper.localPort,
                this.amount, this.branchId, this.accountId);

        if (Main.doDelayTransfers) {
            // sleep for a few seconds before sending the TransferDeposit
            // message to simulate network delays
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
            }
        }

        if (wrapper.sendReliable(depositMessage) == null) {
            // if the destination is unreachable, we send an error
            // notification
            ConnectionError err = new ConnectionError(this.transactionId,
                    this.branchId, this.destBranchId);
            err.fromPort = wrapper.localPort;
            wrapper.sendAcknowledgment(err, this.fromPort);
        } else {
            // send a reply to the GUI
            response.fromPort = wrapper.localPort;
            wrapper.sendAcknowledgment(response, this.fromPort);
        }

    }

    @Override
    public Acknowledgment runRequestSilent(BankBranch bankBranch) {

        int balance;

        // skip use of the network for internal transfers
        if (this.destBranchId == this.branchId) {

            if (this.accountId == this.destAccountId) {
                // don't do a transfer if account and dest account are the
                // same
                System.out.println("Transfer of money between same account: "
                        + this.accountId + " at branch " + this.branchId);
                balance = bankBranch.getAccount(this.accountId).query(
                        this.transactionId);
            } else { // transfer between accounts at the same branch
                System.out.println("Transfering " + this.amount + " between "
                        + this.accountId + " and " + this.destAccountId
                        + " at branch " + this.branchId);
                balance = bankBranch.getAccount(this.accountId).deposit(
                        -this.amount, this.transactionId);
                bankBranch.getAccount(this.destAccountId).deposit(this.amount,
                        this.transactionId);
            }
        } else {

            // use the network to transfer money
            System.out.println("Transfering " + this.amount + " from account "
                    + this.accountId + " at branch " + this.branchId);
            // if the destination is reachable, we decrement our balance
            balance = bankBranch.getAccount(this.accountId).deposit(
                    -this.amount, this.transactionId);
        }

        // send a reply to the GUI
        BalanceResponse message = new BalanceResponse(this.transactionId,
                this.accountId, this.branchId, balance);
        return message;
    }
}
